package ua.kratik.seabattle.game;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ua.kratik.seabattle.game.enums.CellState;

public abstract class SeaMap {

	private static Logger logger = LoggerFactory.getLogger(SeaMap.class);

	private Random random = new Random();

	private CellState[][] seaMap;

	/** is used as a buffer */
	private Coordinates[][] coordinatesMap;

	private int seaSizeX;

	private int seaSizeY;

	// ------ Abstract methods -------
	public abstract void cleanMap();

	// ------ Other methods -------
	public SeaMap(int seaSizeX, int seaSizeY) {
		this.seaSizeX = seaSizeX;
		this.seaSizeY = seaSizeY;
		seaMap = new CellState[seaSizeY][seaSizeX];
		coordinatesMap = new Coordinates[seaSizeY][seaSizeX];
		for (int y = 0; y < seaSizeY; y++) {
			for (int x = 0; x < seaSizeX; x++) {
				coordinatesMap[y][x] = new Coordinates(x + 1, y + 1);
			}
		}

		logger.trace("SeaMap was initialized: " + Arrays.deepToString(seaMap));
	}

	public ArrayList<Coordinates[]> getFreeHorizontalCells(int length) {
		ArrayList<Coordinates[]> list = new ArrayList<Coordinates[]>();
		boolean isFree;
		Coordinates[] pos;
		for (int y = 0; y < seaSizeY; y++) {
			for (int x = 0; (x < seaSizeX) && (x + length < seaSizeX); x++) {
				if ((seaMap[y][x] == CellState.EMPTY)) {
					isFree = true;
					for (int i = 1; i < length; i++) {
						if (seaMap[y][x + i] != CellState.EMPTY) {
							isFree = false;
							break;
						}
					}
					if (isFree) {
						pos = new Coordinates[length];
						for (int i = 0; i < length; i++) {
							pos[i] = coordinatesMap[y][x + i];
						}
						list.add(pos);
					}
				}
			}
		}
		if (logger.isTraceEnabled()) {
			StringBuilder b = new StringBuilder();
			b.append("Free horizontal cells: ");
			for (Coordinates[] cc : list) {
				b.append(Arrays.toString(cc));
				b.append("; ");
			}
			logger.trace(b.toString());
		}
		return list;
	}

	public ArrayList<Coordinates[]> getFreeVerticalCells(int length) {
		ArrayList<Coordinates[]> list = new ArrayList<Coordinates[]>();
		boolean isFree;
		Coordinates[] pos;
		for (int x = 0; x < seaSizeX; x++) {
			for (int y = 0; (y < seaSizeY) && (y + length < seaSizeY); y++) {
				if (seaMap[y][x] == CellState.EMPTY) {
					isFree = true;
					for (int i = 1; i < length; i++) {
						if (seaMap[y + i][x] != CellState.EMPTY) {
							isFree = false;
							break;
						}
					}
					if (isFree) {
						pos = new Coordinates[length];
						for (int i = 0; i < length; i++) {
							pos[i] = coordinatesMap[y + i][x];
						}
						list.add(pos);
					}
				}
			}
		}
		if (logger.isTraceEnabled()) {
			StringBuilder b = new StringBuilder();
			b.append("Free vertical cells: ");
			for (Coordinates[] cc : list) {
				b.append(Arrays.toString(cc));
				b.append("; ");
			}
			logger.trace(b.toString());
		}
		return list;
	}

	public Coordinates[] getFreeRandomLocation(Integer size) {
		ArrayList<Coordinates[]> variants;
		if (random.nextBoolean()) {
			// horizontal location
			variants = getFreeHorizontalCells(size);
			if (variants.size() == 0) {
				variants = getFreeVerticalCells(size);
			}
		} else {
			// vertical location
			variants = getFreeVerticalCells(size);
			if (variants.size() == 0) {
				variants = getFreeHorizontalCells(size);
			}
		}
		if (variants.size() == 0) {
			logger.warn("Can not find free place with size: " + size);
			// throw new
			// IncorrectWorkflowException("Can not find free place with size: "
			// + size);
			return new Coordinates[0];
		} else {
			return variants.get(Math.abs(random.nextInt() % variants.size()));
		}
	}

	public CellState[][] getSeaMap() {
		return seaMap;
	}

	protected void fillWholeMap(CellState state) {
		for (int y = 0; y < seaSizeY; y++) {
			for (int x = 0; x < seaSizeX; x++) {
				seaMap[y][x] = state;
			}
		}
	}

	public void fillCells(CellState state, Coordinates... shipCells) {
		for (Coordinates cell : shipCells) {
			setCellState(cell, state);
		}
	}
	
	public void fillCells(CellState state, Collection<Coordinates> shipCells) {
		for (Coordinates cell : shipCells) {
			setCellState(cell, state);
		}
	}

	public CellState getCellState(Coordinates c) {
		return getCellState(c.getX(), c.getY());
	}

	public CellState getCellState(int x, int y) {
		if(x >=1 && x <= seaSizeX && y >= 1 && y <= seaSizeY){
			return seaMap[y - 1][x - 1];
		} else {
			return null;
		}
	}

	protected void setCellState(Coordinates c, CellState state) {
		setCellState(c.getX(), c.getY(), state);
	}

	protected void setCellState(int x, int y, CellState state) {
		if(x >=1 && x <= seaSizeX && y >=1 && y <= seaSizeY){
			seaMap[y - 1][x - 1] = state;
		}else{
			throw new IllegalArgumentException("Coordinates(x=" + x 
					+ "; y=" + y + ") are prohibited");
		}
	}

	public int getSeaSizeX() {
		return seaSizeX;
	}

	public int getSeaSizeY() {
		return seaSizeY;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("SeaMap size = X");
		sb.append(seaSizeX);
		sb.append("-Y");
		sb.append(seaSizeY);
		sb.append("\nmap:\n");
		for (int y = 0; y < seaSizeY; y++) {
			for (int x = 0; x < seaSizeX; x++) {
				sb.append(seaMap[y][x].getId());
				sb.append("-");
			}
			sb.append("\n");
		}
		return sb.toString();
	}

	public Coordinates getCell(int x, int y){
		if(x >=1 && x <= seaSizeX && y >= 1 && y <= seaSizeY){
			return coordinatesMap[y-1][x-1];
		}else{
			return null;
		}
	}
}
